//
// Copyright (c) 2010 Luís Reis
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
parser grammar pinedl;

options
{
    language=CSharp2;
}

@parser::header {
using System.Globalization;
using G=System.Collections.Generic;
using T=Pine.SourceParser.Tree;
using Pine.Lexer;
}
@parser::namespace { Pine.SourceParser }

document returns [T.Document doc ]
	:	{ $doc = new T.Document(); }
	(f=docfunctiondecl {$doc.varDecls.Add($f.decl);})* EOF;

docfunctiondecl returns [T.SimpleVariableDeclaration decl, T.Function func]
	:	{
	$decl = new T.SimpleVariableDeclaration(); $decl.IsConst = true;
	$func = new T.Function();
	}
	f=FUNCTION name=WORD {
	 $decl.Value = $func; $func.metadata["vardecl-fname"] = $decl.Name = $name.Text;
	}
	LPAREN alist=arglist { $func.arguments = $alist.args; } RPAREN
	s=statement
	{
		$func.stmt = $s.stmt;
		$decl.StartToken = $func.StartToken =f.ToSource();
		$decl.EndToken = $func.EndToken = input.LT(-1).ToSource(); 
	};

arglist returns [G.IList<T.FunctionArgument> args, bool isConst, T.Expression e1, object prevkey, object ckey]
	:
	{
		$args = new G.List<T.FunctionArgument>();
		$isConst = true;
		$e1 = null;
		$prevkey = -1.0;
		$ckey = null;
	}
		(
			(
				arg1=ordinary_argument[$isConst, $prevkey] {$args.Add($arg1.arg); $isConst = $arg1.arg.isConst; $prevkey = $arg1.arg.key; }
				(COMMA
					o=ordinary_argument[$isConst, $prevkey] {$args.Add($o.arg); $isConst = $o.arg.isConst; $prevkey = $o.arg.key; }
				)*
				(COMMA v=vararg_argument[$isConst] {$args.Add($v.arg); $isConst = $v.arg.isConst; $prevkey = $v.arg.key; })?
			)
			|
			(a1=vararg_argument[$isConst] {$args.Add($a1.arg);})
		)?;

ordinary_argument [bool isConst, object prevkey] returns [T.FunctionArgument arg, object ckey]
	:
	{
		$ckey = null;
		$arg = new T.FunctionArgument();
		$arg.StartToken = input.LT(1).ToSource();
	}
	(VAR {$isConst = false;} | CONST {$isConst = true;})?
	((n=NUMBER {$ckey = Double.Parse($n.Text, CultureInfo.InvariantCulture); }
		| w=WORD {$ckey = $w.Text; }
		| str=STRINGCONST {$ckey = $str.Text; })
	COLON)?
	{
		if ($ckey == null) {
			if ($prevkey is string) throw new Exception("Invalid argument list: Expected argument key");
			$ckey = ((double) $prevkey)+1;
		}
		$arg.isConst = isConst;
		$arg.key = $ckey;
	}
	name=WORD {$arg.name = $name.Text; }
	(EQUAL exp=expression {$arg.defaultValue = exp; })?
	{$arg.EndToken = input.LT(-1).ToSource(); }
	;

vararg_argument [bool isConst] returns [T.FunctionArgument arg]
	:
	{
		$arg = new T.FunctionArgument();
		$arg.isVarArgs = true;
		$arg.StartToken = input.LT(1).ToSource();
	}
	(VAR { $isConst = false; } | CONST { $isConst = true; })?
	{
		$arg.isConst = $isConst;
	}
	n=WORD {$arg.name = $n.Text; }
	e=ELLIPSIS {$arg.EndToken = e.ToSource(); }
	;

statement returns [T.Statement stmt]
	:	b=block { $stmt = b; }
	| 	n=unitstmt { $stmt = n; }
	|	i=ifstmt { $stmt = $i.stmt; }
	|	w=whilestmt { $stmt = $w.stmt; }
	|	f=forstmt { $stmt = $f.stmt; };

unitstmt returns [T.Statement stmt]
:	(s = semicolonstmt {$stmt = s;} SEMICOLON {$s.stmt.EndToken = input.LT(-1).ToSource(); })
	| SEMICOLON { $stmt = new T.EmptyStatement(); $stmt.StartToken = $stmt.EndToken = input.LT(-1).ToSource(); }
	;

semicolonstmt returns [T.Statement stmt]:
	(RETURN ELLIPSIS)=>r2=returnellipsisstmt { $stmt = $r2.stmt; }
	| ((WORD COLON)? DO)=> dow=dowhilestmt { $stmt = $dow.stmt; }
	| r=returnstmt { $stmt = $r.stmt; }
	| (assignmentrightlist)=>a=assignmentrightlist { $stmt = new T.AssignmentStatement($a.rl); $stmt.StartToken = $a.rl.StartToken; }
	| e=expressionstmt { $stmt = $e.stmt; }
	| d=declarationstmt { $stmt = $d.stmt; }
	| b=breakstmt { $stmt = $b.stmt; }
	| c=continuestmt { $stmt = $c.stmt; }
	;

expressionstmt returns [T.ExpressionStatement stmt]
	:
		(e=expression {$stmt = new T.ExpressionStatement($e.e); $stmt.StartToken = $e.e.StartToken; });

block	returns [T.Block stmt]
	:	{ $stmt = new T.Block(); $stmt.StartToken = input.LT(1).ToSource(); }
		LBLOCK (s=statement {$stmt.content.Add($s.stmt); })* RBLOCK
		{ $stmt.EndToken = input.LT(-1).ToSource(); };

returnstmt returns [T.ReturnStatement stmt]
	:	ret=RETURN rl=rightlist {$stmt = new T.ReturnStatement($rl.r); $stmt.StartToken = ret.ToSource(); };

returnellipsisstmt returns [T.ReturnEllipsisStatement stmt]
	:	ret=RETURN ELLIPSIS a=atom {$stmt = new T.ReturnEllipsisStatement($a.e); $stmt.StartToken = ret.ToSource(); };

breakstmt returns [T.BreakStatement stmt, string loopName]
	:
	{$loopName = null;}
	b=BREAK (w=WORD {$loopName = w.Text;})? {$stmt = new T.BreakStatement($loopName); $stmt.StartToken = b.ToSource(); };

continuestmt returns [T.ContinueStatement stmt, string loopName]
	:
	{$loopName = null;}
	c=CONTINUE (w=WORD {$loopName = w.Text;})? {$stmt = new T.ContinueStatement($loopName); $stmt.StartToken = c.ToSource(); };

ifstmt returns [T.ConditionalStatement stmt, T.Statement elsestmt]
	:	{$elsestmt = null;}
	iftoken=IF LPAREN condition=expression RPAREN
	i=statement
	((ELSE) => ELSE e=statement {$elsestmt = $e.stmt;})?
	{$stmt = new T.ConditionalStatement($condition.e, $i.stmt, $elsestmt); $stmt.StartToken = iftoken.ToSource(); $stmt.EndToken = input.LT(-1).ToSource(); };

whilestmt returns [string name, T.WhileStatement stmt]
	: {$name = null;  }
	(w=WORD {$name = $w.Text;} COLON)? /*For named whiles*/
	WHILE LPAREN condition=expression RPAREN
	s=statement
	{$stmt = new T.WhileStatement($name, $condition.e, $s.stmt); $stmt.StartToken = ((IToken) retval.Start).ToSource(); $stmt.EndToken = input.LT(-1).ToSource(); };
	
dowhilestmt returns [string name, T.DoWhileStatement stmt]
	: {$name = null; }
	(w=WORD {$name = $w.Text;} COLON)? /*For named whiles*/
	DO
	s=statement
	WHILE LPAREN condition=expression RPAREN
	{$stmt = new T.DoWhileStatement($name, $condition.e, $s.stmt); $stmt.StartToken = ((IToken) retval.Start).ToSource(); };

forstmt returns [string name, T.ForStatement stmt, T.Expression e, T.Statement endStmt] :
	{ $name = null; }
	(w=WORD {$name = $w.Text;} COLON)? /*For named fors*/
	/*Perhaps change condition to be right list instead of expression?*/
	FOR LPAREN begin=unitstmt (c = expression {$e = $c.e;})? SEMICOLON (end=expressionstmt {$endStmt = $end.stmt;})? RPAREN
	content=statement
	{$stmt = new T.ForStatement($name, $begin.stmt, $e, $endStmt, $content.stmt); $stmt.StartToken = ((IToken) retval.Start).ToSource(); $stmt.EndToken = $content.stmt.EndToken; };

arrayconst returns [T.ArrayConstant a]:
	l=LARRAY
	{$a = new T.ArrayConstant(); $a.StartToken = l.ToSource(); }
	(
		e1=expression ARRAYKEYVALUE e2=expression { $a.arrayMembers.Add(new T.ArrayMember($e1.e, $e2.e)); }
		(COMMA e3=expression ARRAYKEYVALUE e4=expression { $a.arrayMembers.Add(new T.ArrayMember($e3.e, $e4.e)); })*
	)? r=RARRAY {$a.EndToken = r.ToSource(); }
;

typeconst returns [T.TypeConstant t]:
	num=NUMBERKEYWORD {$t = new T.TypeConstant(T.TypeConstantType.Number); $t.StartToken = $t.EndToken = num.ToSource(); }
	;

constant returns [T.Constant c]
	: n=NUMBER {$c = new T.NumberConstant( Double.Parse($n.Text, CultureInfo.InvariantCulture)); $c.StartToken = $c.EndToken = n.ToSource(); }
	| s=STRINGCONST { $c = new T.StringConstant($s.Text); $c.StartToken = $c.EndToken = n.ToSource(); }
	| ch=CHARCONST {$c = new T.CharConstant($ch.Text[0]); $c.StartToken = $c.EndToken = ch.ToSource(); }
	| tr=TRUE {$c = new T.BooleanConstant(true); $c.StartToken = $c.EndToken = tr.ToSource(); }
	| fa=FALSE {$c = new T.BooleanConstant(false); $c.StartToken = $c.EndToken = fa.ToSource(); }
	| nullToken=NULL {$c = new T.NullConstant(); $c.StartToken = $c.EndToken = nullToken.ToSource(); }
	| THIS {$c = null; }
	| CALLEE {$c = null; }
	| a=arrayconst { $c = $a.a; }
	| f=functiondecl {$c = $f.f; }
	| t=typeconst { $c = $t.t; };

functiondecl returns [T.Function f]:
	func=FUNCTION
	{$f = new T.Function(); $f.StartToken = func.ToSource(); }
	LPAREN alist=arglist { $f.arguments = $alist.args; } RPAREN
	b=block {$f.stmt=$b.stmt; $f.EndToken = $b.stmt.EndToken; }
	;

rightlist returns [T.RightList r]:
	(assignmentrightlist)=>arl = assignmentrightlist { $r = $arl.rl; } |
	crl = constantrightlist { $r = $crl.crl; };
	
constantrightlist returns [T.ConstantRightList crl, object lastkey]:
	{
		$lastkey = -1.0;
		$crl = new T.ConstantRightList();
		$crl.contents = new G.Dictionary<object, T.Expression>();
		$crl.StartToken = ((IToken) retval.Start).ToSource();
	}
	(c=crlargument[ref $lastkey] {$crl.contents[$lastkey] = $c.e;}
	(COMMA c2=crlargument[ref $lastkey] {$crl.contents[$lastkey] = $c2.e;})*)?
	{$crl.EndToken = input.LT(-1).ToSource(); }
	;

assignmentrightlist returns [T.AssignmentRightList rl]:
	als=assignmentlistleftside
	EQUAL vrl=rightlist
	{$rl = new T.AssignmentRightList($als.als, $vrl.r); $rl.StartToken = $als.als.StartToken; $rl.EndToken = $vrl.r.EndToken; }
	;

assignmentlistleftside returns [T.AssignmentListLeftSide als, object lastkey]:
	{
		$lastkey = -1.0;
		$als = new T.AssignmentListLeftSide();
		$als.contents = new G.Dictionary<object, T.Expression>();
		$als.StartToken =((IToken) retval.Start).ToSource();
	}
	c=alsargument[ref $lastkey] { $als.contents[$lastkey] = $c.e; }
	(COMMA c2=alsargument[ref $lastkey] {$als.contents[$lastkey] = $c2.e; })*
	{ $als.EndToken = input.LT(-1).ToSource(); }
	;

alsargument [ref object lastkey] returns [T.Expression e]:
	(((w=WORD {$lastkey = w.Text;}|s=STRINGCONST{$lastkey=$s.Text;}|n=NUMBER {$lastkey=Double.Parse($n.Text, CultureInfo.InvariantCulture);}) COLON)
	| {
	if (!($lastkey is double))
		throw new Exception("Could not guess list key");
	$lastkey = ((double) $lastkey) + 1;
	})
	exp=expression {$e = $exp.e; }
	;

declarationlist returns [T.VariableDeclarationLeftList list, bool isConst, object key]
	:
	{
		$list = new T.VariableDeclarationLeftList();
		$key = 0.0;
		$isConst = false;
		$list.StartToken = ((IToken) retval.Start).ToSource();
	}
	(VAR | CONST {$isConst = true;})
	((w=WORD {$key=$w.Text;}|s=STRINGCONST {$key=$s.Text;}|n=NUMBER {$key=Double.Parse($n.Text, CultureInfo.InvariantCulture);}) COLON)?
	name=WORD
	{
		var decl = new T.VariableDeclarationElement($isConst, $key, $name.Text, false);
		$list.declarations.Add(decl);
	}
	{ $list.EndToken = input.LT(-1).ToSource(); }
	
	//TODO
	//(COMMA )*
	;

declarationstmt returns [T.DeclarationStatement stmt, T.RightList rl]
	:
	l=declarationlist
	(EQUAL r=rightlist {$rl=$r.r;})?
	{
		$stmt = new T.DeclarationStatement($l.list, $rl);
		$stmt.StartToken = $l.list.StartToken;
		$stmt.EndToken = input.LT(-1).ToSource();
	}
	;

crlargument [ref object lastkey] returns [T.Expression e]:
	(((w=WORD {$lastkey=$w.Text;}|x=STRINGCONST {lastkey=$x.Text;}|n=NUMBER {lastkey=Double.Parse($n.Text, CultureInfo.InvariantCulture);}) COLON)
	| {
	if(!($lastkey is double))
		throw new Exception("Could not guess list key");
	$lastkey = ((double) $lastkey) + 1;
	}) exp=expression {$e=$exp.e;}
;

atom returns [T.Expression e, Antlr.Runtime.IToken start]:
	(c=constant {$e = $c.c; $start = input.LT(1); }
	| i=WORD {$e = new T.NameIdentifier($i.Text); $e.StartToken = $e.EndToken = i.ToSource(); }
	| left=LPAREN r=rightlist right=RPAREN {$e = new T.ListCompression($r.r); $e.StartToken = left.ToSource(); $e.EndToken = right.ToSource(); })
	
	((DOT f=WORD {$e = new T.FieldReference($e, $f.Text); $e.StartToken = $start.ToSource(); $e.EndToken = f.ToSource(); })
	| (LPAREN r=rightlist right=RPAREN {$e = new T.CallExpression($e, $r.r); $e.StartToken = $start.ToSource(); $e.EndToken = right.ToSource(); })
	| (LARRAY idx=expression right=RARRAY {$e = new T.ArrayAccessExpression($e, $idx.e); $e.StartToken = $start.ToSource(); $e.EndToken = right.ToSource(); })
	)*
	;

pfix returns [T.Expression e]:
	b=atom {$e = $b.e;}
	(INCREMENT {$e = new T.PostfixExpression($e, true);} | DECREMENT {$e = new T.PostfixExpression($e, false);})?
	{$e.StartToken = $b.e.StartToken; $e.EndToken = input.LT(-1).ToSource(); }
	;

unary returns [T.Expression e, T.UnaryExpressionType type]:
	p=pfix {$e = $p.e;}
	|(
	(
	INCREMENT {$type = T.UnaryExpressionType.Increment;}
	|DECREMENT {$type = T.UnaryExpressionType.Decrement;}
	|PLUS {$type = T.UnaryExpressionType.Positive;}
	|MINUS {$type = T.UnaryExpressionType.Negative;}
	|BITNOT {$type = T.UnaryExpressionType.BinaryNegation;}
	|LOGNOT {$type = T.UnaryExpressionType.LogicalNegation;})
	f=pfix {$e = new T.UnaryExpression($f.e, $type); $e.StartToken = ((IToken) retval.Start).ToSource(); $e.EndToken = $f.e.EndToken; });

mult returns [T.Expression e, T.OperationType type]:
	a1=unary {$e = $a1.e;}
	{$type = default(T.OperationType);}
	((MULT { $type = T.OperationType.Multiplication; })
	a2=unary {$e = new T.BinaryOperation($type, $e, $a2.e); $e.StartToken = $a1.e.StartToken; $e.EndToken = input.LT(-1).ToSource(); })*
	;

plus returns [T.Expression e, T.OperationType type]:
	a1=mult {$e = $a1.e;}
	{$type = default(T.OperationType);}
	((PLUS { $type = T.OperationType.Sum; } | MINUS {$type = T.OperationType.Minus; })
	a2=mult {$e = new T.BinaryOperation($type, $e, $a2.e); $e.StartToken = $a1.e.StartToken; $e.EndToken = $a2.e.EndToken; })*
	;

compare returns [T.Expression e, T.OperationType type]:
	a1=plus {$e = $a1.e;}
	{$type = default(T.OperationType);}
	((LESS { $type = T.OperationType.Less; } |
	LESSOREQUAL {$type = T.OperationType.LessOrEqual;} |
	GREATER {$type = T.OperationType.Greater; } |
	GREATEROREQUAL {$type = T.OperationType.GreaterOrEqual; }
	) /*is and has come here too*/
	a2=plus {$e = new T.BinaryOperation($type, $e, $a2.e); $e.StartToken = $a1.e.StartToken; $e.EndToken = $a2.e.EndToken; })*
	;

equality returns [T.Expression e, T.OperationType type]:
	a1=compare {$e = $a1.e;}
	{$type = default(T.OperationType);}
	((EQUALS { $type = T.OperationType.Equal; } | NOTEQUAL {$type = T.OperationType.NotEqual; })
	a2=compare {$e = new T.BinaryOperation($type, $e, $a2.e); $e.StartToken = $a1.e.StartToken; $e.EndToken = $a2.e.EndToken; })*
	;

/*TODO: Bitwise operators */

logicaland returns [T.Expression e]:
	a1=equality /*in the future, bitwise operators here*/ {$e = $a1.e; }
	(LOGICALAND a2=equality {$e = new T.BinaryOperation(T.OperationType.LogicalAnd, $e, $a2.e); $e.StartToken = $a1.e.StartToken; $e.EndToken = $a2.e.EndToken; })*;

logicalor returns [T.Expression e]:
	a1=logicaland {$e = $a1.e; }
	(LOGICALOR a2=logicaland {$e = new T.BinaryOperation(T.OperationType.LogicalOr, $e, $a2.e); $e.StartToken = $a1.e.StartToken; $e.EndToken = $a2.e.EndToken; })*;

expression returns [T.Expression e]
	:	(c=logicalor {$e=$c.e;});

/*CONST	:	'const';
VAR	:	'var';
FOR	:	'for';
FALSE	:	'false';
NULL	:	'null';
FUNCTION:	'function';
CALLEE	:	'callee';
THIS	:	'this';
IF	:	'if';
ELSE	:	'else';
TRUE	:	'true';
WHILE	:	'while';
NUMBERKEYWORD
	:	'number';
RETURN	:	'return';

CHARCONST
	:	'\'' '\'';
STRINGCONST
	:	'"' '"';

EQUAL	:	'=';
INCREMENT
	:	'++';
DECREMENT
	:	'--';
COLON	:	':';
COMMA	:	',';
ARRAYKEYVALUE
	:	'=>';
ELLIPSIS:	'...';
SEMICOLON
	:	';';
DOT	:	'.';
EQUALS	:	'==';
PLUS	:	'+';
MULT	:	'*';
MINUS	:	'-';
LPAREN	:	'(';
RPAREN	:	')';
GREATEROREQUAL
	:	'>=';
NOTEQUAL:	'!=';
LBLOCK	:	'{';
RBLOCK	:	'}';
LARRAY	:	'[';
RARRAY	:	']';
LOGNOT	:	'!';
BITNOT	:	'~';
LESSOREQUAL
	:	'<=';
LESS	:	'<';
GREATER	:	'>';
WORD	:	ALPHA (ALPHA|DIGIT)*;
NUMBER	:	DIGIT (DIGIT*);
fragment ALPHA
	:	'a'..'z'|'A'..'Z'|'_';
fragment DIGIT
	:	'0'..'9';*/
